#error "not useable"

#include <algorithm>

#include "./common.hpp"

namespace data_basic {

    /**
     * @brief 双缓冲，主要用于DMA 或中断异步传输屏幕显示数据的场合
     *
     * 双缓冲传输是异步的，相比单缓冲，还存在状态反馈滞后的问题。比如，生产者构造第N - 1 帧数据后swap，N - 1 帧开始由DMA 传输，
     * 生产者同时构造第N 帧，将数据复制到写入缓冲。第N 帧完成后，生产者准备swap，这时如果第N - 1 还没发完，生产者就原地阻塞；
     * 如果N - 1 帧发完了，那么生产者或许有必要获取传输结果，如果出错了，可能还要重新发送N - 1 帧。
     *
     * 也就是说，N - 1 帧数据的状态是在N + 1 帧开始构造之前同步给生产者的，这种状态滞后会给上层代码设计造成很多不便。但是屏幕数据刷新
     * 不怎么在乎传输状态，只管发送就行了。这时双缓冲就像是两级FIFO，如果N + 1 帧数据在N 帧发完之后才准备好，传输就完全没有阻塞；
     * 即使N 帧发的比较慢，N + 1 在swap 时阻塞，卡住的时间也相对比较短。
     *
     * 需要即时确认的少量数据传输不适合双缓冲，比如读取、写入传感器。可以将多个WireInterface 对象绑定到一个硬件I2C 外设上，每个对象内部
     * 可以采用不同的传输实现，但也因此需要在传输开始前切换不同的I2C 事件回调函数，与接下来要开始传输的对象匹配。
     *
     * @tparam T
     * @tparam N
     * @tparam _IndexType
     */
    template <typename T, size_t N, typename _IndexType = enough_and_fast_index_type<N>>
    class DoubleBuffer {
        static_assert(N > 0);

       public:
        using DataType = T;
        using IndexType = _IndexType;

        static constexpr size_t BufferSize = N;

       private:
        T _buf_0[N];
        T _buf_1[N];

        // 这两个指针有可能在中断函数里swap，但是通过_reading 和_writing 状态的保护，
        // swap 只会发生在指针操作结束后，把状态变量标记为volatile 就行了
        // 算了，还是加上volatile 稍微放心一点，或许不如直接把对象整体标记为volatile
        T *volatile _ptr_for_reader = &_buf_0[0];
        T *volatile _ptr_for_writer = &_buf_1[0];

        //
        volatile IndexType _read_index = 0;
        volatile IndexType _write_index = 0;
        volatile IndexType _write_count = 0;

        volatile bool _reading = false;
        volatile bool _writing = false;

        volatile bool _swapped = false;  // 已发生一次缓冲交换，由生产者置位，消费者清零

        bool _can_be_swapped() const {
            return !(_reading || _writing);
        }

       public:
        /**
         * @brief 交换缓冲区，必须由写入者执行。
         *
         * @return true
         * @return false
         */
        bool try_swap() {
            if (_can_be_swapped()) {
                // 缓冲区交换后，写入缓冲变成读缓冲交给消费者，消费者需要知道缓冲区里写入了多少东西，
                // 所以要把write_index 赋值给wrote_counter

                // TODO: 多线程环境有必要在swap 时加锁。但如果写入和读取一边是主程序，一边是中断，则不用加锁
                // 因为中断总能打断主程序，而中断执行swap 不会被主程序打断；
                // 如果swap 由主程序执行，表示写入者是主程序，中断不会改变对象的内部状态，所以不用加锁。
                auto tmp = _ptr_for_reader;
                _ptr_for_reader = _ptr_for_writer;
                _ptr_for_writer = tmp;

                _write_count = _write_index;
                return true;
            }
            else {
                return false;
            }
        }

        /**
         * @brief
         *
         * @return T*
         */
        const T *begin_read() {
            _reading = true;
            _read_index = 0;
            return _ptr_for_reader;
        }

        T *begin_write() {
            _writing = true;
            _write_index = 0;
            return _ptr_for_writer;
        }


        /**
         * @brief 只能由消费者调用，确定是否发生了一次缓冲交换
         *
         * @return true
         * @return false
         */
        bool swapped() {
            bool t = _swapped;
            _swapped = false;
            return t;
        }

        /**
         * @brief swap 后，读取缓冲区是否已读完
         *
         * 只使用于调用read 一个一个读的情形
         *
         * @return true
         * @return false
         */
        bool not_empty() const {
            return _read_index < _write_count;
        }

        /**
         * @brief 写入缓冲区是否已写满
         *
         * 只适用于调用write 一个一个写的情形
         *
         * @return true
         * @return false
         */
        bool not_full() const {
            return _write_index < N;
        }

        IndexType max_count() const {
            return N;
        }

        IndexType write_count() const {
            return _write_count;
        }

        IndexType write_index() const {
            return _write_index;
        }

        IndexType read_index() const {
            return _read_index;
        }

        bool reading() const {
            return _reading;
        }

        bool writing() const {
            return _writing;
        }

        /**
         * @brief 结束读取
         *
         * 消费者必须在end_read 前完成数据处理，调用end_read 后，生产者随时可能执行swap。
         *
         */
        void end_read() {
            _reading = false;
        }

        /**
         * @brief 结束写入
         *
         * @param wrote 写入的类型T 数据个数
         */
        void end_write(IndexType wrote = 0) {
            _write_index += wrote;
            _writing = false;
        }

        void write(T d) {
            _ptr_for_writer[_write_index] = d;
            if (_write_index < N) {
                ++_write_index;
            }
        }

        T read() {
            T d = _ptr_for_reader[_read_index];
            if (_read_index < N) {
                ++_read_index;
            }
        }

        void reset() {
            end_read();
            end_write();

            _swapped = false;
            _write_count = 0;
        }
    };

}  // namespace data_basic